/*
 * ProtocolByIdFinder.java
 *
 * Created on March 27, 2007, 11:09 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.app;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.sql.SQLException;
import java.util.UUID;
import java.util.logging.Level;
import org.atomojo.app.db.DB;
import org.atomojo.app.db.Entry;
import org.atomojo.app.db.EntryMedia;
import org.atomojo.app.db.Feed;
import org.infoset.xml.Document;
import org.infoset.xml.DocumentLoader;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.infoset.xml.sax.SAXDocumentLoader;
import org.infoset.xml.util.XMLWriter;
import org.restlet.Application;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.data.Tag;
import org.restlet.representation.Representation;
import org.restlet.resource.Finder;
import org.restlet.resource.ServerResource;

/**
 *
 * @author alex
 */
public class ProtocolByPathFinder extends Finder {

   
   DB atomDB;
   Application theApp;
   Reference resourceBase;
   DocumentLoader loader = new SAXDocumentLoader();
   Storage storage;
   App app;
   
   /*
   public static String join(String [] values, int start, int length,char delimiter) {
      StringBuilder buffer = new StringBuilder();
      int end = start+length;
      for (int i=start; i<end; i++) {
         if (i!=start) {
            buffer.append(delimiter);
         }
         buffer.append(values[i]);
      }
      return buffer.toString();
   }*/

   /** Creates a new instance of ProtocolByIdFinder */
   public ProtocolByPathFinder(Context context,Application theApp,Reference resourceBase,App app,DB atomDB,Storage storage) {
      super(context);
      this.theApp = theApp;
      this.atomDB = atomDB;
      this.resourceBase = resourceBase;
      this.storage = storage;
      this.app = app;
      setTargetClass(ServerResource.class);
   }
   
   public ServerResource find(Request request, Response response) {
      String uriPath = request.getResourceRef().getRemainingPart();
      if (uriPath==null) {
         uriPath = request.getResourceRef().getPath();
      }
      if (uriPath.equals("/")) {
         uriPath = "";
      }
      int q = uriPath.indexOf('?');
      if (q>=0) {
         uriPath = uriPath.substring(0,q);
      }
      if (getLogger().isLoggable(Level.FINE)) {
         getLogger().fine("by-path, uriPath = '"+uriPath+"'");
      }
      int uriPathLen = uriPath.length();
      String [] segments = uriPath.split("/");
      int last = segments.length-1;
      String file = null;
      if (uriPathLen>0 && uriPath.charAt(uriPathLen-1)!='/' && segments[last].length()>2 && segments[last].indexOf('.')>0) {
         file = segments[last];
      }
      if (file==null) {
         // it should be a feed or entry
         // test for entry
         if (segments.length>=2 && segments[last-1].equals("_")) {
            String [] path = new String[segments.length-2];
            System.arraycopy(segments,0,path,0,segments.length-2);
            UUID entryId = UUID.fromString(segments[last]);

            String feedPath = App.join(path,0,path.length,'/')+'/';
            try {
               Feed f = app.getFeed(feedPath);
               if (f!=null) {
                  try {
                     Entry entry = f.findEntry(entryId);
                     if (entry!=null) {
                        EntryResource r = new EntryResource(theApp,f,entry,storage);
                        return r;
                     }
                  } catch (SQLException ex) {
                     getContext().getLogger().log(Level.SEVERE,"Failed to retrieve entry at "+entryId+" from the database.",ex);
                     return new ErrorResource(theApp.getStatusService().getRepresentation(Status.SERVER_ERROR_INTERNAL,request,response));
                  }
               }
               return null;
            } catch (AppException ex) {
               if (ex.getStatus().getCode()==Status.CLIENT_ERROR_NOT_FOUND.getCode()) {
                  return null;
               } else {
                  getContext().getLogger().log(Level.SEVERE,"Failed to retrieve feed at "+feedPath+" from the database.",ex);
                  return new ErrorResource(theApp.getStatusService().getRepresentation(Status.SERVER_ERROR_INTERNAL,request,response));
               }
            }
         } else {
            try {

               Feed f = app.getFeed(uriPath);
               if (uriPath.length()>0 && !uriPath.endsWith("/")) {
                  uriPath += "/";
               }
               Reference feedResourceRef = new Reference(request.getRootRef().toString()+resourceBase.toString()+uriPath);
               FeedResource r = new FeedResource(theApp,f,feedResourceRef,storage);
               return r;
            } catch (AppException ex) {
               if (ex.getStatus().getCode()==Status.CLIENT_ERROR_NOT_FOUND.getCode()) {
                  // It is possible we have a media entry with no '.' in the name, so test for a media entry
                  if (segments.length>0) {
                     String [] path = new String[segments.length-1];
                     System.arraycopy(segments,0,path,0,segments.length-1);
                     String feedPath = App.join(path,0,path.length,'/');
                     file = segments[segments.length-1];
                     try {
                        Feed f = app.getFeed(feedPath);
                        EntryMedia media = f.findEntryResource(file);
                        if (media!=null) {
                           MediaResource r = new MediaResource(theApp,f,media,storage);
                           return r;
                        } else {
                           //getLogger().warning("No media resource "+file);
                           return null;
                        }
                     } catch (AppException nex) {
                        if (ex.getStatus().getCode()==Status.CLIENT_ERROR_NOT_FOUND.getCode()) {
                           return null;
                        } else {
                           getContext().getLogger().log(Level.SEVERE,"Failed to retrieve feed at "+feedPath+" from the database.",nex);
                           return new ErrorResource(theApp.getStatusService().getRepresentation(Status.SERVER_ERROR_INTERNAL,request,response));
                        }

                     } catch (SQLException nex) {
                        getContext().getLogger().log(Level.SEVERE,"Failed to retrieve entry media "+file+" from feed at "+feedPath+" from the database.",nex);
                        return new ErrorResource(theApp.getStatusService().getRepresentation(Status.SERVER_ERROR_INTERNAL,request,response));
                     }
                  }
                  return null;
               } else {
                  getContext().getLogger().log(Level.SEVERE,"Failed to retrieve feed at "+uriPath+" from the database.",ex);
                  return new ErrorResource(theApp.getStatusService().getRepresentation(Status.SERVER_ERROR_INTERNAL,request,response));
               }
            }
         }
      } else {
         String [] path = new String[segments.length-1];
         System.arraycopy(segments,0,path,0,segments.length-1);
         String feedPath = App.join(path,0,path.length,'/');
         if (file.startsWith("_entry_.")) {
            try {
               Feed f = app.getFeed(feedPath);
               if (f!=null) {
                  String entryIdS = file.substring(8);
                  int end = entryIdS.indexOf(".");
                  if (end<0) {
                     return null;
                  }
                  if (!entryIdS.substring(end).equals(".atom")) {
                     return null;
                  }
                  entryIdS = entryIdS.substring(0,end);
                  try {
                     UUID entryId = UUID.fromString(entryIdS);
                     Entry entry = f.findEntry(entryId);
                     if (entry!=null) {
                        EntryResource r = new EntryResource(theApp,f,entry,storage);
                        return r;
                     }
                  } catch (SQLException ex) {
                     getContext().getLogger().log(Level.SEVERE,"Failed to retrieve entry at "+entryIdS+" from the database.",ex);
                     return new ErrorResource(theApp.getStatusService().getRepresentation(Status.SERVER_ERROR_INTERNAL,request,response));
                  } catch (IllegalArgumentException ex) {
                     return new ErrorResource(theApp.getStatusService().getRepresentation(Status.CLIENT_ERROR_BAD_REQUEST,request,response));
                  }
               }
               return null;
            } catch (AppException ex) {
               if (ex.getStatus().getCode()==Status.CLIENT_ERROR_NOT_FOUND.getCode()) {
                  return null;
               } else {
                  getContext().getLogger().log(Level.SEVERE,"Failed to retrieve feed at "+feedPath+" from the database.",ex);
                  return new ErrorResource(theApp.getStatusService().getRepresentation(Status.SERVER_ERROR_INTERNAL,request,response));
               }
            }
         } else if (file.charAt(0)=='.' && file.endsWith(".atom")) {
            return new ErrorResource(theApp.getStatusService().getRepresentation(Status.CLIENT_ERROR_BAD_REQUEST,request,response));
         } else {
            try {
               // The media name is already encoded
               file = URLDecoder.decode(file,"UTF-8");
               file = URLEncoder.encode(file,"UTF-8");
               Feed f = app.getFeed(feedPath);
               EntryMedia media = f.findEntryResource(file);
               if (media!=null) {
                  MediaResource r = new MediaResource(theApp,f,media,storage);
                  return r;
               } else {
                  //getLogger().warning("No media resource "+file);
                  return null;
               }
            } catch (AppException ex) {
               if (ex.getStatus().getCode()==Status.CLIENT_ERROR_NOT_FOUND.getCode()) {
                  return null;
               } else {
                  getContext().getLogger().log(Level.SEVERE,"Failed to retrieve feed at "+feedPath+" from the database.",ex);
                  return new ErrorResource(theApp.getStatusService().getRepresentation(Status.SERVER_ERROR_INTERNAL,request,response));
               }

            } catch (SQLException ex) {
               getContext().getLogger().log(Level.SEVERE,"Failed to retrieve entry media "+file+" from feed at "+feedPath+" from the database.",ex);
               return new ErrorResource(theApp.getStatusService().getRepresentation(Status.SERVER_ERROR_INTERNAL,request,response));
            } catch (UnsupportedEncodingException ex) {
               getContext().getLogger().log(Level.SEVERE,"Failed to decode "+file,ex);
               return new ErrorResource(theApp.getStatusService().getRepresentation(Status.SERVER_ERROR_INTERNAL,request,response));
            }
         }
      }
   }
   
   public void handle(Request request, Response response) {
      if (request.getMethod().equals(Method.POST)) {
         if (request.getEntity().getMediaType().getName().equals(MediaType.APPLICATION_ATOM_XML.getName())) {
            String charset = request.getEntity().getMediaType().getParameters().getValues("charset");
            if (charset==null) {
               charset = "UTF-8";
            }
            try {
               Reader r = new InputStreamReader(request.getEntity().getStream(),charset);
               Document doc = loader.load(r);
               r.close();

               Name top = doc.getDocumentElement().getName();
               if (top.equals(AtomResource.FEED_NAME)) {
                  String requestPath = request.getResourceRef().getRemainingPart();
                  if (requestPath==null) {
                     requestPath = "";
                  }
                  if (getLogger().isLoggable(Level.FINE)) {
                     getLogger().fine("Creating feed at '"+requestPath+"'");
                  }
                  

                  try {
                     Feed f = app.createFeed(requestPath,doc);
                     response.setStatus(Status.SUCCESS_CREATED);
                     Representation rep = storage.getFeed(f.getPath(),f.getUUID(),f.getEntries());
                     if (rep!=null) {
                        rep.setTag(new Tag(Long.toString(f.getEdited().getTime()),false));
                     }
                     response.setEntity(rep);
                  } catch (AppException ex) {
                     if (ex.getStatus().equals(Status.SERVER_ERROR_INTERNAL)) {
                        getLogger().log(Level.SEVERE,ex.getMessage(),ex);
                     }
                     response.setStatus(ex.getStatus(),ex.getMessage());
                  }
                  
                  
                  
               } else if (top.equals(AtomResource.ENTRY_NAME)) {
                  super.handle(new NewEntryRequest(request,doc),response);
               } else {
                  response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST,"Unrecognized document element: "+top);
               }
            } catch (XMLException ex) {
               response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST,"Cannot parse content: "+ex.getMessage());
            } catch (Exception ex) {
               getContext().getLogger().log(Level.SEVERE,"Fatal exception during post:"+ex.getMessage(),ex);
               response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST,"Fatal error "+ex.getMessage());
            }
         } else {
            super.handle(request,response);
         }
      } else {
         super.handle(request,response);
      }
   }
   
   String serialize(Document doc)
      throws XMLException,IOException
   {
      StringWriter w = new StringWriter();
      XMLWriter.writeDocument(doc,w);
      return w.toString();
   }
   
}
